package controller;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import model.algorithm.AbstractAlgorithm;
import model.algorithm.AbstractSurfaceIterator;
import model.datastructure.Block2D;
import model.datastructure.Dimension2D;
import model.datastructure.Field2D;
import model.io.BlockGenerator2D;
import model.io.IniReader;
import util.HeapSort;

public class Main {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		Main main = new Main();
		main.runTest();
	}

	/**
	 * runTest
	 * Collects config info from the config file and builds the runs
	 */
	public void runTest() {
		
		/* Take the file with config information and read it */
		String fileName = "config.ini";
		IniReader reader = new IniReader( fileName );

		/* Create blockLists */
		List<List<Block2D>> blockLists = new ArrayList<List<Block2D>>();

		/* Get block info from the reader */
		int[] blockAmounts = reader.getBlockamount();
		String blockFile = reader.getBlockfile();
		
		/* Build blockLists */
		for( int blockAmount : blockAmounts ){
			try {
				blockLists.add( BlockGenerator2D.getBlocks2D(BlockGenerator2D.getBlocksTypes2DFromFile( blockFile ), blockAmount) );
			} catch (IOException e1) {
				e1.printStackTrace();
			}
		}
		
		/* Take info from the reader */
		int[] fieldWidths = reader.getFieldWidths();
		int[] lookAheads = reader.getLookaheads();
		int[] lookAheadTypes = reader.getLookaheadTypes();
		int timeout = reader.getTimeout();

		/* Just to show how many different setups we're running */
		
		/* Checking how much partitioned and nonpartitioned iterators influence the amount of setups */
		int partitionedMultiplier = 0;
		int nonPartitionedMultiplier = 0;
		for( String iterator : reader.getIterators() ) {
			if( iterator.equals("partitioned") ){
				partitionedMultiplier++;
			} else {
				nonPartitionedMultiplier++;
			}
		}
		
		int iteratorMultiplier = partitionedMultiplier * reader.getNumberOfPartitionWidths() + nonPartitionedMultiplier;
		
		/* Calculate permutations of the runned algorithms */
		int setupAmount = fieldWidths.length * blockLists.size() * reader.getNumberOfAlgorithms() * iteratorMultiplier * reader.getLookaheads().length * reader.getLookaheadTypes().length;
		System.out.println("Testing " + setupAmount + " algorithm combinations: ");
		
		/* Create a list with scores to compare and print afterwards */
			
		for( int fieldWidth : fieldWidths ){
			System.out.println("Testing with field width " + fieldWidth);
			for( List<Block2D> blocks : blockLists ) {				
				System.out.println("Testing with " + blocks.size() + " blocks");
				List<Score> results = new ArrayList<Score>();	
				
				/* Get iterators based on the fieldWidth */
				List<AbstractSurfaceIterator> iterators = reader.getIterators( fieldWidth );
				
				/* Get algorithms based on the created iterators */
				for( AbstractAlgorithm algorithm : reader.getAlgorithms( iterators )){
					
					for (int lookAheadType : lookAheadTypes) {
						for (int lookAhead : lookAheads) {
						
							/* Create a dimension for the fieldwidth and pass it when creating a new Field */
							Dimension2D dimension = new Dimension2D(fieldWidth, 0);
							Field2D field = new Field2D(dimension);
							
							/* Make a copy of the current blockList */
							List<Block2D> newBlocks = BlockGenerator2D.deepcopyBlockList2D(blocks);
							
							/* Set some variables to provide a report afterwards */							
							Score score = new Score();
							score.setAlgorithm(algorithm);
							score.setLookAhead(lookAhead);
							score.setLookAheadType(lookAheadType);
							long start = System.nanoTime();
							
							/* Now let the algorithm do its magic */
							try {
								algorithm.placeBlocks(field, newBlocks, lookAhead, timeout, lookAheadType);
								
								/* When the algorithm is done, store variables to provide reporting */
								long end = System.nanoTime();
								score.setExecutedTime(end - start);
								score.setFieldHeight(field.getDimension().getHeight());
								
								/* Print out info, so we know something happened */
								System.out.println(algorithm + "field width: " + fieldWidth + "\tlook ahead: (T " + lookAheadType + ") " + lookAhead + ", finished");
								
							} catch (Exception e) {
								System.err.println("Got an error placing blocks: " + e.getMessage());
								// e.printStackTrace();
							}
							
							results.add(score);
						}
						
						/* Sort the scores and print them */
						HeapSort sorter = new HeapSort();
						int counter = 1;
						System.out.println();
						System.out.println("Max time per algorithm: " + timeout + "s");
						System.out.println("Scores:");
						for (Comparable<?> score : sorter.sortAsc(results)) {
							System.out.println(counter + ". " + score);
							counter++;
						}
						System.out.println("----------------------------------\n\n");
					}
				}
			}
		}

		
		
	}
	
	/**
	 * getExecutionTime
	 * 
	 * @param start
	 * @param end
	 * @return String, execution time
	 */
	private static String getExecutionTime(long start, long end) {
		long time = end - start;
		if (time > 1000000000)
			return ("executed in: " + (time / 10000000) / 100.0 + " s");
		else if (time > 1000000)
			return ("executed in: " + (time / 10000) / 100.0 + " ms");
		else if (time > 1000)
			return ("executed in: " + (time / 10) / 100.0 + " us");
		else
			return ("executed in: " + (time) + " ns");
	}
	
	/**
	 * Score
	 * HelperClass to store the run informations in and compare afterwards 
	 */
	public class Score implements Comparable<Object> {

		private Long executedTime;
		private Integer fieldHeight;
		private AbstractAlgorithm algorithm;
		private int lookAhead;
		private int lookAheadType;

		/**
		 * @param executedTime
		 *            the executedTime to set
		 */
		public void setExecutedTime(long executedTime) {
			this.executedTime = executedTime;
		}

		/**
		 * @param score
		 *            the score to set
		 */
		public void setFieldHeight(Integer score) {
			this.fieldHeight = score;
		}

		/**
		 * @param algorithm
		 *            the algorithm to set
		 */
		public void setAlgorithm(AbstractAlgorithm algorithm) {
			this.algorithm = algorithm;
		}

		/**
		 * @param lookAhead
		 *            the lookAhead to set
		 */
		public void setLookAhead(int lookAhead) {
			this.lookAhead = lookAhead;
		}

		/**
		 * @param lookAheadType
		 *            the lookAheadType to set
		 */
		public void setLookAheadType(int lookAheadType) {
			this.lookAheadType = lookAheadType;
		}

		public int compareTo(Object o) {
			assert o.getClass() == this.getClass();
			Score other = (Score) o;
			int result = 0;
			if (this.fieldHeight == null) {
				if (other.fieldHeight != null) {
					result = 1;
				} else {
					result = 0;
				}
			} else {
				if (other.fieldHeight == null) {
					result = -1;
				} else {
					result = this.fieldHeight.compareTo(other.fieldHeight);
					if (result == 0) {
						result = this.executedTime
								.compareTo(other.executedTime);
					}
				}
			}

			return result;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#toString()
		 */
		@Override
		public String toString() {
			String type = null;
			if (this.lookAheadType == AbstractAlgorithm.LOOKAHEAD_PLACEALL) {
				type = "PlaceAll";
			} else if (this.lookAheadType == AbstractAlgorithm.LOOKAHEAD_PLACEONE) {
				type = "PlaceOne";
			} else {
				type = "Unknown";
			}
			String result = algorithm.toString() + "look ahead: "
					+ this.lookAhead + "\t" + "type: " + type + "\t";
			if (fieldHeight != null) {
				result += " field height: " + fieldHeight + " in "
						+ getExecutionTime(0, executedTime);
			} else {
				result += "DID NOT FINISH";
			}

			return result;
		}
	}

}